package web

import (
	"fmt"
	"regexp"
	"strings"
)

type nodeType int

const (
	// 静态路由
	nodeTypeStatic = iota
	// 正则路由
	nodeTypeReg
	// 路径参数路由
	nodeTypeParam
	// 通配符路由
	nodeTypeAny
)

func (r *router) addRoute(method string, path string, handleFunc HandleFunc) {
	if path == "" {
		panic("web: 路径不能为空字符串")
	}
	// 首先找到树
	root, ok := r.trees[method]
	if !ok {
		//说明还没有根节点
		root = &node{
			path: "/",
		}
		r.trees[method] = root
	}
	// 开头不能没有/
	if path[0] != '/' {
		panic("web 路径必须比 / 开头")
	}
	// 不能以/结尾
	if path != "/" && path[len(path)-1] == '/' {
		panic("web: 路径不能以 / 结尾")
	}
	// 中间连续 //，可以用 strings.contains("//")

	// 根节点特殊处理一下
	if path == "/" {
		// 根节点重复注册
		if root.handler != nil {
			panic("web: 路由冲突，重复注册[/]")
		}
		root.handler = handleFunc
		return
	}
	// 需要切割, 不然会多出来一段空的
	path = path[1:]
	//切割
	segs := strings.Split(path, "/")
	for _, seg := range segs {
		// 递归
		// 中间节点不存在，新建节点
		child := root.childOrCreate(seg)
		root = child
	}
	if root.handler != nil {
		panic(fmt.Sprintf("web: 路由冲突，重复注册[%s]", path))
	}
	root.handler = handleFunc
}

func (r *router) findRoute(method string, path string) (*matchInfo, bool) {
	// 沿着数深层查找
	root, ok := r.trees[method]
	if !ok {
		return nil, false
	}
	if path == "/" {
		return &matchInfo{
			n: root,
		}, true
	}
	// 把前置和后置的 / 都去掉
	path = strings.Trim(path, "/")
	// 按照斜杠切割
	mi := &matchInfo{}
	segs := strings.Split(path, "/")
	for _, seg := range segs {
		var child *node
		child, ok = root.childOf(seg)
		if !ok {
			if root.typ == nodeTypeAny {
				mi.n = root
				return mi, true
			}
			return nil, false
		}
		//命中了路径参数
		if child.paramName != "" {
			mi.addValue(child.paramName, seg)
		}
		root = child
	}
	// 代表我确实有这个节点
	// 但是节点是不是用户注册的有 handler 的，就不一定了
	//return root, root.handler != nil
	mi.n = root
	return mi, true
}

// childOf 优先考虑静态匹配，再考虑通配符匹配
// 第一个返回值是子节点
// 第二个返回值是是否是路径参数
// 第三个返回值是命中了没有
func (n *node) childOf(path string) (*node, bool) {
	// 查找子节点
	if n.children == nil {
		return n.childOfNotStatic(path)
	}
	child, ok := n.children[path]
	if !ok {
		if n.paramChild != nil {
			return n.paramChild, true
		}
		return n.childOfNotStatic(path)
	}
	return child, ok
}

// 从非静态匹配的子节点里面查询
func (n *node) childOfNotStatic(path string) (*node, bool) {
	//正则
	if n.regExpr != nil {
		if n.regChild.regExpr.Match([]byte(path)) {
			return n.regChild, true
		}
	}
	if n.paramChild != nil {
		return n.paramChild, true
	}
	return n.starChild, n.handler != nil
}

// 用来支持路由树的操作,森林 GET POST每个方法对应一棵树
type router struct {
	// http method =>路由树根节点
	trees map[string]*node
}

func newRouter() *router {
	return &router{
		trees: map[string]*node{},
	}
}

type node struct {
	// 给个标记看是否是通配符匹配
	typ  nodeType
	path string
	// 通配符匹配
	starChild *node
	// 子path 到子节点的映射
	children map[string]*node
	// 缺一个用户注册的业务逻辑
	// 路径参数
	paramChild *node
	handler    HandleFunc
	// 正则路由和参数路由都会使用这个字段
	paramName string
	// 正则表达式
	regChild *node
	regExpr  *regexp.Regexp
}

// 根据路径查找节点，没有则新建有则直接返回
func (n *node) childOrCreate(seg string) *node {
	if seg == "*" {
		if n.paramChild != nil {
			panic("web: 不允许同时注册路径参数和通配符匹配,已有路径参数匹配")
		}
		if n.regChild != nil {
			panic(fmt.Sprintf("web: 非法路由，已有正则路由。不允许同时注册通配符路由和正则路由 [%s]", seg))
		}
		if n.starChild == nil {
			n.starChild = &node{
				path: seg,
				// 通配符
				typ: nodeTypeAny,
			}
		}
		return n.starChild
	}
	// 以 : 开头，需要进一步解析，判断是参数路由还是正则路由
	if seg[0] == ':' {
		paramName, expr, isReg := n.parseParam(seg)
		if isReg {
			return n.childOrCreateReg(seg, expr, paramName)
		}
		return n.childOrCreateParam(seg, paramName)
	}

	if n.children == nil {
		n.children = make(map[string]*node)
	}
	res, ok := n.children[seg]
	if !ok {
		// 要新建一个
		res = &node{
			path: seg,
			// 静态路由
			typ: nodeTypeStatic,
		}
		n.children[seg] = res
	}
	return res
}

func (n *node) parseParam(path string) (string, string, bool) {
	// 去掉:
	path = path[1:]
	// paramName xxx
	segs := strings.SplitN(path, "(", 2)
	if len(segs) == 2 {
		expr := segs[1]
		if strings.HasSuffix(expr, ")") {
			return segs[0], expr[:len(expr)-1], true
		}
	}
	return path, "", false
}

func (n *node) childOrCreateParam(path string, paramName string) *node {
	if n.regChild != nil {
		panic(fmt.Sprintf("web: 非法路由，已有正则路由。不允许同时注册正则路由和参数路由 [%s]", path))
	}
	if n.starChild != nil {
		panic(fmt.Sprintf("web: 非法路由，已有通配符路由。不允许同时注册通配符路由和参数路由 [%s]", path))
	}
	if n.paramChild != nil {
		if n.paramChild.path != path {
			panic(fmt.Sprintf("web: 路由冲突，参数路由冲突，已有 %s，新注册 %s", n.paramChild.path, path))
		}
	} else {
		n.paramChild = &node{path: path, paramName: paramName, typ: nodeTypeParam}
	}
	return n.paramChild
}

func (n *node) childOrCreateReg(path string, expr string, paramName string) *node {
	if n.starChild != nil {
		panic(fmt.Sprintf("web: 非法路由，已有通配符路由。不允许同时注册通配符路由和参数路由 [%s]", path))
	}
	if n.paramChild != nil {
		if n.paramChild.path != path {
			panic(fmt.Sprintf("web: 路由冲突，参数路由冲突，已有 %s，新注册 %s", n.paramChild.path, path))
		}
		if n.regChild != nil {
			if n.regChild.regExpr.String() != expr || n.paramName != paramName {
				panic(fmt.Sprintf("web: 路由冲突，正则路由冲突，已有 %s，新注册 %s", n.regChild.path, path))
			}
		} else {
			regExpr, err := regexp.Compile(expr)
			if err != nil {
				panic(fmt.Errorf("web: 正则表达式错误 %w", err))
			}
			n.regChild = &node{path: path, paramName: paramName, regExpr: regExpr, typ: nodeTypeReg}
		}
	}
	return n.regChild
}

type matchInfo struct {
	n          *node
	pathParams map[string]string
}

func (m *matchInfo) addValue(key string, value string) {
	if m.pathParams == nil {
		m.pathParams = map[string]string{key: value}
	}
	m.pathParams[key] = value
}
